using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;

namespace VerticalProcesses
{
    public class ClimateForcing
    {
        protected DateTime currentDateTime;
        protected string nameOfFirstStation;
        protected double taThresh = 0.0;               // Temperature threshold for rain vs. snow
        protected double taRange = 1.0;              // Temperature interval for mixed rain and snow
        protected Dictionary<string, ClimateStation> climateStations = new Dictionary<string, ClimateStation>();
        public double TimeStepHrs { get; set; }
        public Dictionary<string, ClimateStation> ClimateStations
        {
            get { return climateStations; }
        }
        public bool MoveAllToNextObservation()
        {
            bool lineRead = true;
            foreach (KeyValuePair<string, ClimateStation> climateStation in climateStations)
            {
                lineRead = climateStation.Value.MoveToNextObservation();
            }
            currentDateTime = climateStations[nameOfFirstStation].CurrentDateTime;
            return lineRead;
        }
        public DateTime CurrentDateTime
        {
            get { return currentDateTime; }
            set{currentDateTime= value;}
        }

        public ClimateForcing(Dictionary<string, string> metFiles, DateTime[] timeSpan, double timeStepHrs, double taThresh, double taRange, string evapoEq)
        {
            // Store all metfiles in a Dictionary and move each file to startdate            
            int indexer = 1;
            foreach (KeyValuePair<string, string> metfile in metFiles)
            {
                if (indexer == 1)
                {
                    nameOfFirstStation = metfile.Key;
                }
                climateStations.Add(metfile.Key, new ClimateStation(metfile.Value));
                climateStations[metfile.Key].MoveToObservationDate(timeSpan[0]);
                indexer++;
            }
            this.taThresh = taThresh;
            this.taRange = taRange;
            TimeStepHrs = timeStepHrs;
       
        }
 
        public Dictionary<string,double> regionalizeForcings(string stationName, double gruElevation, double latitude, double longitude, double timeZone, double slope, double aspect)
        {
            ClimateStation currentStation = climateStations[stationName];
            Dictionary<string, double> gruForcing = new Dictionary<string, double>();
            
            //at this time only daily and hourly time steps are supported
            if (TimeStepHrs == 1)
            {
                double gruTa = Interpolation.LinearChangeWithElevation(currentStation.ClimatePosition[2], currentStation.CurrentClimateObservations["TA"], gruElevation, currentStation.CurrentClimateObservations["T_LAPSE"]);//-0.00546);//          
                gruForcing.Add("ta", gruTa);
            }
            if (TimeStepHrs == 24)
            {
                double gruTmin = Interpolation.LinearChangeWithElevation(currentStation.ClimatePosition[2], currentStation.CurrentClimateObservations["TMIN"], gruElevation, currentStation.CurrentClimateObservations["T_LAPSE"]);//-0.00546);//
                double gruTmax = Interpolation.LinearChangeWithElevation(currentStation.ClimatePosition[2], currentStation.CurrentClimateObservations["TMAX"], gruElevation, currentStation.CurrentClimateObservations["T_LAPSE"]);//-0.00546);//    
                gruForcing.Add("ta", (gruTmin + gruTmax) / 2);
                gruForcing.Add("tmin", gruTmin);
                gruForcing.Add("tmax", gruTmax);
            }
            else
            {
                throw new NotImplementedException();
            }

            double gruP = Interpolation.FractionalChangeWithElevation(currentStation.ClimatePosition[2], currentStation.CurrentClimateObservations["P"], gruElevation, currentStation.CurrentClimateObservations["P_LAPSE"]);//0.000317566);// ;                       
            
            //double gruP = Interpolation.FractionalChangeWithElevation(currentStation.ClimatePosition[2], currentStation.CurrentClimateObservations["P"], gruElevation, 0.000317566);//0.000317566);// ;                                  
            double rainFraction = RainFraction(gruForcing["ta"], taThresh, taRange);
            gruForcing.Add("rain", gruP * rainFraction);
            gruForcing.Add("snow", gruP * (1 - rainFraction));

            //this is a construction site
            //again, when we decide where to go, we can move the selection of different equations into the constructor
            //and code wont break
            //gruForcing.Add("shortrad", RadiationEq.ClearSkySolarRadiationOld(currentStation.CurrentDateTime, timeStepHrs, latitude, longitude, gruElevation, timeZone, slope, aspect));          
            gruForcing.Add("shortrad", RadiationEq.CalcClearSkySolarRadiation(currentStation.CurrentDateTime, TimeStepHrs, latitude, longitude, gruElevation, timeZone, slope, aspect));
            //Kustas
            //gruForcing.Add("shortrad", RadiationEq.CalcRadSlopeAspect(currentStation.CurrentDateTime, currentStation.CurrentClimateObservations["SOLAR"], latitude, slope, aspect));
            return gruForcing;
        }
        /// <summary>
        /// Computes the fraction of precipitation occurring as rainfall based on current
        /// air temperature 
        /// </summary>
        /// <param name="temperature">Curent air temperature</param>
        /// <returns>Fraction (0,1)</returns>
        private double RainFraction(double temperature, double threshold, double range)
        {
            if (temperature >= (threshold + range)) return 1.0;
            if (temperature <= (threshold - range)) return 0.0;

            // mixed rain/snow
            double frac = (temperature - threshold + range) / (2 * range);
            return frac;
        }

    }

    
    /// <summary>
    /// This class handles reading the climate input file sequentially (timestep-by-timestep).
    /// </summary>
    public class ClimateStation
    {
        private string fileName;
        private StreamReader climateReader;
        private static int filePosition = 0;
        private string[] headerVariables;
        private string identifier = "Undefined";
        private double[] position = new double[3] { 0.0, 0.0, 0.0 };
        private double timeStepHrs = 1.0;
        private DateTime currentDate;
        private Dictionary<string, double> currentObservedVariables;

        #region ClimateClass Properties

        /// <summary>
        /// Position (x,y,z) of climate station
        /// </summary>
        public double[] ClimatePosition
        {
            get { return position; }
        }

        /// <summary>
        /// Climate station identifier
        /// </summary>
        public string ClimateIdentifier
        {
            get { return identifier;  }
        }

        /// <summary>
        /// Climate reader
        /// </summary>
        public StreamReader ClimateReader
        {
            get { return climateReader;  }
            set { climateReader = value; }
        }
        ///<summary>
        /// Hours per Timestep
        /// </summary>
        public double TimeStepHrs
        {
            get { return timeStepHrs; }
        }

        /// <summary>
        /// An array of available variables at climate station
        /// </summary>
        public string[] AvailableVariables
        {
            get { return headerVariables; }
        }

        /// <summary>
        /// Date/time of current observation
        /// </summary>
        public DateTime CurrentDateTime 
        {
            get { return currentDate; }
        }

        /// <summary>
        /// available climate variables
        /// </summary>
        public Dictionary<string,double> CurrentClimateObservations
        {
            get { return currentObservedVariables; }
        }

        #endregion


        #region ClimateClass Methods

        public void MoveToObservationDate(DateTime Date)
        {
            while (currentDate != Date & !climateReader.EndOfStream)
            {
                bool eof = MoveToNextObservation();
            }

            if ( climateReader.EndOfStream ) 
            {
                string errMsg = string.Format("Date {0} not found in input file {1}", Date, fileName);
                throw new Exception ( errMsg );
            }

        }
        public bool MoveToNextObservation()
        {

            if (!climateReader.EndOfStream)
            {
                string oneline = climateReader.ReadLine();
                return parseLine(oneline);
            }
            else
            {
                return false;
            }
        }
        

        #endregion


        #region ClimateClass Constructor

        public ClimateStation(string fileName)
        {
            this.fileName = fileName;
            initializeClimateFile(fileName);
           
        }

        ClimateStation()
        {
            climateReader.Close();
        }

        # endregion


        #region Private methods

        private void initializeClimateFile(string aFile)
        {
            Regex regKeyValue = new Regex(@"(\w+):\s*(.+)");

            climateReader = new StreamReader(aFile);
            string oneline = null;
            while ((oneline = climateReader.ReadLine()) != null)
            {
                if (oneline.ToUpper().StartsWith("ENDHEADER"))
                {
                    break;
                }

                Match m = regKeyValue.Match(oneline);
                if (m.Success)
                {
                    switch (m.Groups[1].ToString().ToUpper())
                    {
                        case "NAME":
                            identifier = m.Groups[2].ToString();
                            break;
                        case "POSITION":
                            position = extractPosition(m.Groups[2].ToString());
                            //_Position = convertStringToDouble(m.Groups[2].ToString().Split(new char[] { ',' }));
                            break;
                        case "VARIABLENAMES":
                            headerVariables = Regex.Split(m.Groups[2].ToString().ToUpper(), "\\s+");
                            break;
                        case "TIMESTEPHRS":
                            timeStepHrs = Convert.ToDouble(m.Groups[2].ToString());
                            break;
                    }
                }
            }
        }


        private bool parseLine(string aLine)
        {
            filePosition++;

            if (aLine.Trim().Length > 0)
            {
                string[] lineElements = Regex.Split(aLine, "\\s+",RegexOptions.IgnorePatternWhitespace);

                if (lineElements.Length != headerVariables.Length + 2)
                {
                    string errMsg = String.Format(
                                        "Number of variables found does not match header at line {0} of climate file ({1})",
                                        filePosition,
                                        fileName);
                    throw new Exception(errMsg);
                }

                currentDate = Convert.ToDateTime(String.Concat(lineElements[0], " ", lineElements[1]));
                currentObservedVariables = new Dictionary<string,double>();
                for (int i = 0; i < headerVariables.Length; i++)
                {
                    currentObservedVariables[headerVariables[i]] = Convert.ToDouble(lineElements[i + 2]);
                }

                return true;
            }
            else
            {
                return false;
            }
            
        }

        private double[] extractPosition(string positionString)
        {
            double[] position = new double[3];
            string[] pos = Regex.Split(positionString, "\\s+");
            for (int i = 0; i < 3; i++)
            {
                position[i] = Convert.ToDouble(pos[i]);
            }

            return position;
        }

        #endregion


    }
}
